#include "crtmpprotocol.h"
#include "camf0stream.h"
#include "cmonitor.h"

#include <signal.h>

#include <cstdlib>
#include <cstring>
#include <cstdio>
#include <iostream>
#include <sstream>
using namespace std;


/////////////////////////////////////////////////////////////////////////////////
// CONSTRUCTORS / DESTRUCTOR
/////////////////////////////////////////////////////////////////////////////////

CRtmpProtocol::CRtmpProtocol(int iSocket)
{
    // we are connected
    m_bConnectionAlive = true;

    //m_pServerSocket = _pServerSocket;
    m_iSocket = iSocket;

    //default chunk  size
    m_iChunkSize = 128;
}

CRtmpProtocol::~CRtmpProtocol()
{

}

/////////////////////////////////////////////////////////////////////////////////
// PUBLIC FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////


bool CRtmpProtocol::Handshake()
{
    // Easy handsake.. first we load 1537 bytes and sent it to client back and with next 1536 bytes same again..

    CPacket _packet;

    try
    {
        _packet = Read(1537);
        cout << "Handshake part 1 size: " << _packet.GetLength() << endl;
        Write(_packet);
        _packet = Read(1536);
        cout << "Handshake part 2 size: " << _packet.GetLength() << endl;
        Write(_packet);
    }
    catch (CRtmpException e)
    {
        CMonitor::DrawErrMessage("Handshake error.");
        return false;
    }

    return true;
}

bool CRtmpProtocol::GetNextMessage(CRtmpMessage &rtmpMessage)
{
    CRtmpChunk _chunk;
    CPacket _packet;
    int _iPayloadSize = 0; // for count payload size to read
    int _iChunkStreamID = 0;

    // Load one all packet. This packet can be divided to several chunks. This is reason for while..
    while (true)
    {
        try
        {
            if (!CheckBuffer())
                return false;
        }
        catch (CRtmpException &e)
        {
            CMonitor::DrawErrMessage("CheckBuffer Error.");
        }

        // read chunk
        try
        {
            _chunk = ReadChunk();
        }
        catch (CRtmpException &e)
        {
            CMonitor::DrawErrMessage("ReadChunk can't be done.");

            // connection interupted
            if (e.GetException() == RtmpError::ERR_CONNECTION_INTERUPTED)
            {
                m_bConnectionAlive = false;
            }
            return false;
        }

        CMonitor::Draw(_chunk);

        // get Chunk stream ID
        _iChunkStreamID = _chunk.GetCsId();

        // read chunk to the  right stream
        m_mapChunkStreams[_iChunkStreamID].ReadChunk(_chunk);

        //count payload size
        _iPayloadSize = m_mapChunkStreams[_iChunkStreamID].GetPayloadLen() -
                        m_mapChunkStreams[_iChunkStreamID].GetLoadedPayloadLen();
        // if remaining payload > max chunk size than we can load only max chunk size
        if (_iPayloadSize > m_iChunkSize)
            _iPayloadSize = m_iChunkSize;

        //read payload
        try
        {
            _packet = Read(_iPayloadSize);
        }
        catch (CRtmpException e)
        {
            CMonitor::DrawErrMessage("Can't to load all packet payload.");
            return false;
        }

        // Push payload data from packet to the stream field
        m_mapChunkStreams[_iChunkStreamID].PushToPayload(string(_packet.GetBuffer(),_iPayloadSize));

        // if message is fully loaded than we can proceed message and send answer
        if (m_mapChunkStreams[_iChunkStreamID].GetPayloadLen() ==
                        m_mapChunkStreams[_iChunkStreamID].GetLoadedPayloadLen())
        {
            // Loaded all chunk
            rtmpMessage = m_mapChunkStreams[_iChunkStreamID];
            m_mapChunkStreams[_iChunkStreamID].PopPayload();
            return true;
        }
        else
        {
            // Loaded chunk isnt all
        }
    }
    return false;
}

bool CRtmpProtocol::SendMessage(const CRtmpMessage &rtmpMessage, int iCsID, int type)
{
    string _strOutMessage = CreatePacket(rtmpMessage, iCsID, type);
    CPacket _packet;
    _packet.SetBuffer(_strOutMessage.data(),_strOutMessage.size());
    Write(_packet);
    return true;
}

bool CRtmpProtocol::IsAlive() const
{
    return m_bConnectionAlive;
}

/////////////////////////////////////////////////////////////////////////////////
// PRIVATE FUNCTIONS
/////////////////////////////////////////////////////////////////////////////////

std::string CRtmpProtocol::CreatePacket(CRtmpMessage message, int iCsID, int type)
{
    string _out;
    string _payload = message.PopPayload();

    CRtmpChunk _chunk;
    _chunk.SetCsId(iCsID);
    _chunk.SetType(CRtmpChunk::ChunkType(type));
    _chunk.SetMsgLen(message.GetPayloadLen());
    _chunk.SetMsgTypeId(message.GetMsgType());
    _chunk.SetStreamId(message.GetStreamId());
    _chunk.SetTimestamp(message.GetTimestamp());

    _out = _chunk.GetEncodeChunk();

    _chunk.SetType(CRtmpChunk::RTMP_CHNK_T3);
    while (_payload.size() > (unsigned int)m_iChunkSize)
    {
        _out.append(_payload.substr(0,m_iChunkSize));
        _payload.erase(0,m_iChunkSize);

        _out.append(_chunk.GetEncodeChunk());

    }
    _out.append(_payload);


    return _out;
}


CRtmpChunk  CRtmpProtocol::ReadChunk()
{
    CRtmpChunk _out;

    CPacket _packet;
    _packet = Read(1);

    // Format type is represented by first two bits in first byte of Chunk Basic header. 0x60 is for anding first two bits.
    _out.SetType(CRtmpChunk::ChunkType(((int)_packet.GetBufferU()[0] & 0xC0) >> 6));

    // Chunk stream ID is stored in 0 - 5 bits. If this nuber 0 or 1 it signs chunk basic header type 2 or 3.
    _out.SetCsId((int)_packet.GetBufferU()[0] & 0x1F);
    if (_out.GetCsId() == 0)
    {
        _packet = Read(1);
        // Chunk basic header 2 has chunk stream ID in second byte + 64
         _out.SetCsId((int)_packet.GetBufferU()[0] + 64);

    }
    if (_out.GetCsId() == 1)
    {
        _packet = Read(2);
        // Chunk basic header 3 has chunk stream ID in third byte * 256 + second byte + 64
        _out.SetCsId((int)_packet.GetBufferU()[1]*256 + (int)_packet.GetBufferU()[0] + 64);
    }

    // Chunk type 0, 1 and 2 has timestamp section
    if (_out.GetType() == CRtmpChunk::RTMP_CHNK_T0 || _out.GetType() == CRtmpChunk::RTMP_CHNK_T1 ||
        _out.GetType() == CRtmpChunk::RTMP_CHNK_T2)
    {
        _packet = Read(3);
        _out.SetTimestamp((int(_packet.GetBufferU()[0]) << 16) + (int(_packet.GetBufferU()[1]) << 8) + int(_packet.GetBufferU()[2]));

    }

    // Chunk type 0 and 1 has message length and message type section too
    if (_out.GetType() == CRtmpChunk::RTMP_CHNK_T0 || _out.GetType() == CRtmpChunk::RTMP_CHNK_T1)
    {
        _packet = Read(4);
        _out.SetMsgLen((int(_packet.GetBufferU()[0]) << 16) + (int(_packet.GetBufferU()[1]) << 8) + int(_packet.GetBufferU()[2]));
        _out.SetMsgTypeId(int(_packet.GetBufferU()[3]));

    }

    // In addition chunk type 0 contains stream id
    if (_out.GetType() == CRtmpChunk::RTMP_CHNK_T0)
    {
        _packet = Read(4);
        _out.SetStreamId((int(_packet.GetBufferU()[0]) << 24) + (int(_packet.GetBufferU()[1]) << 16) +
                         (int(_packet.GetBufferU()[2]) << 8) + (int(_packet.GetBufferU()[3])));
    }

    if (_out.GetTimestamp() == 0x00FFFFFF)
    {
        _packet = Read(4);
        _out.SetTimestamp((int(_packet.GetBufferU()[0]) << 24) + (int(_packet.GetBufferU()[1]) << 16) +
                         (int(_packet.GetBufferU()[2]) << 8) + (int(_packet.GetBufferU()[3])));
    }

    return _out;
}


int  CRtmpProtocol::Write(CPacket packet) const throw (CRtmpException &)
{
    int _iSentCount = write(m_iSocket,packet.GetBuffer(),packet.GetLength());
    if (_iSentCount < packet.GetLength()) // All data wasn't sent
        throw CRtmpException(RtmpError::ERR_SEND_DATA);
    return _iSentCount;

    // @TODO transform to use Socket library
    //return m_pServerSocket->SendData(m_iSocket,&packet);
}

CPacket  CRtmpProtocol::Read(int iSize) const throw (CRtmpException &)
{
    char _pucBuffer[iSize];
    bzero(_pucBuffer,iSize);
    int _iLoaded = 0;
    int _iRemains = iSize;
    do
    {
        _iLoaded += read(m_iSocket, _pucBuffer + _iLoaded, _iRemains);
        _iRemains -= _iLoaded;
        if (_iLoaded < 0) // Can't read data
            throw CRtmpException(RtmpError::ERR_RECV_DATA);
        if (_iLoaded == 0) // something wrong
            throw CRtmpException(RtmpError::ERR_CONNECTION_INTERUPTED);
    } while (_iLoaded < iSize);
    
    return CPacket(_pucBuffer, _iLoaded);

    // @TODO transform to use Socket library
    // @TODO wait few ms to get all data
/*    CPacket _packet;
    m_pServerSocket->RecieveData(m_iSocket,&_packet);
    return _packet;*/
}

bool CRtmpProtocol::CheckBuffer() const throw (CRtmpException &)
{
  fd_set _read;
  timeval _timeout = {0, 1};
  _timeout.tv_sec = 0;

  FD_ZERO(&_read);
  FD_SET(m_iSocket, &_read);
  int n;
  n = select(FD_SETSIZE, &_read, NULL, NULL, &_timeout);

  


  return n;

  // @TODO transform to use Socket library
}
